/**
 * Implementacja semafora z wykorzystaniem semaforów IPC SV, umożliwiająca
 * opuszczanie semafora z ustalonym maksymalnym czasem oczekiwania (timeout).
 */

#include "semaphore.h"

/* Flaga określająca, czy blokada na semaforze została przerwana przez SIGALRM */
volatile int _sem_timedout = 0;

void sem_sigalrm_handler(int signum);


/**
 * Zwraca identyfikator semafora skojarzony z podanym kluczem lub tworzy go,
 * jeżeli nie istnieje, a następnie inicjalizuje go podaną wartością.
 * W przypadku błędu zwraca -1.
 */
int _linda_sem_init(key_t key, int init_val)
{
    int sem_id;
    
    // Próba utworzenia semafora
    if ( (sem_id = semget(key, 1, 0600 | IPC_CREAT)) == -1)
        return -1;
    
    // Inicjalizacja semafora
    if (semctl(sem_id, 0, SETVAL, init_val) == -1)
        return -1;
    
    return sem_id;
}


/**
 * Zwraca identyfikator semafora skojarzony z podanym kluczem.
 * W przypadku błędu (w szczególności jeżeli semafor nie istnieje) zwraca -1.
 */
int _linda_sem_get(key_t key)
{
    return semget(key, 0, 0600);
}


/**
 * Dekrementuje wartość semafora (jeśli jest > 0), w przeciwnym razie usypia 
 * proces. Jeżeli określony został timeout (w sekundach), to po jego upłynięciu
 * do procesu zgłaszany jest SIGALRM.
 * Zwraca 0 jesli operacja zakończy się pomyślnie, 1 jeżeli upłynął timeout,
 * -1 jeżeli wystąpił inny błąd związny z wywołaniem semop.
 */
int _linda_sem_P(int sem_id, unsigned int timeout)
{
    struct sembuf semb;
    int opres;                  // rezultat semop
    
    semb.sem_num = 0;
    semb.sem_op = -1;           // op: dekrementacja semafora
    semb.sem_flg = SEM_UNDO;    // wycofanie operacji po śmierci procesu
    
    // Ustawienie obsługi SIGALRM, jeśli określony został timeout
    if (timeout > 0)
    {
        _sem_timedout = 0;
        signal(SIGALRM, sem_sigalrm_handler);
        alarm(timeout);
    }
    
    opres = semop(sem_id, &semb, 1);
    
    // Wyłączenie alarmu
    if (timeout > 0)
        alarm(0);
    
    if (opres == -1)
    {
        if (errno == EINTR && _sem_timedout == 1)
            // Upłynął timeout
            return 1;
        else
            // Inny błąd semop
            return -1;
    }
    
    return 0;
}


/**
 * Inkrementuje wartość semafora.
 * Zwraca 0 jeśli operacja zakończy się pomyślnie, -1 w przypadku błędu.
 */
int _linda_sem_V(int sem_id)
{
    struct sembuf semb;
    
    semb.sem_num = 0;
    semb.sem_op = 1;            // op: inkrementacja semafora
    semb.sem_flg = SEM_UNDO;    // wycofanie operacji po śmierci procesu
    
    if (semop(sem_id, &semb, 1) == -1)
        return -1;
    else
        return 0;
}


/**
 * Zwalnia zasoby systemowe semafora o podanym sem_id.
 * Zwraca 0 jeśli operacja zakończy się pomyślnie, -1 w przypadku błędu.
 */
int _linda_sem_destroy(int sem_id)
{
    return semctl(sem_id, 0, IPC_RMID);
}


/**
 * Funkcja obsługi sygnału SIGARLM.
 */
void sem_sigalrm_handler(int signum)
{
    _sem_timedout = 1;
}
